Autopudate#162
Conversation
There was a problem hiding this comment.
Code Review
This pull request removes unused imports and the fallback re-enrollment logic from the middleware, and updates the verification cache key to include the protocol and target to prevent authorization cache bypasses. It also adds a unit test to verify this fix. The review feedback suggests using a null byte (\x00) instead of a colon (:) as a delimiter in the cache key to prevent potential collision attacks, and closing the pipe writer in the test goroutine to prevent potential test hangs.
| // Check verification cache | ||
| tokenHash := sha256.Sum256(authFrame.Biscuit) | ||
| hashStr := hex.EncodeToString(tokenHash[:]) + ":" + remotePeer.String() | ||
| hashStr := hex.EncodeToString(tokenHash[:]) + ":" + remotePeer.String() + ":" + reqCtx.Protocol + ":" + reqCtx.Target |
There was a problem hiding this comment.
Using a colon (:) as a delimiter for concatenating multiple fields in the cache key can lead to potential cache key collisions if any of the fields (such as reqCtx.Protocol or reqCtx.Target) contain colons. Since reqCtx.Target is derived from the user-controlled authFrame.TargetService, a malicious client could craft a target service name containing colons to collide with another cached authorization entry.
To prevent delimiter collision attacks, use a null byte (\x00) as the separator, as it cannot legitimately appear in standard protocol names or target services.
| hashStr := hex.EncodeToString(tokenHash[:]) + ":" + remotePeer.String() + ":" + reqCtx.Protocol + ":" + reqCtx.Target | |
| hashStr := hex.EncodeToString(tokenHash[:]) + "\x00" + remotePeer.String() + "\x00" + reqCtx.Protocol + "\x00" + reqCtx.Target |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request removes the fallback re-enrollment logic from the middleware and cleans up its associated integration tests. Additionally, it addresses a potential security vulnerability by including the protocol and target in the verification cache key, separated by null delimiters, to prevent authorization cache bypass. A new unit test has been added to verify this cache bypass protection. No review comments were provided, so there is no additional feedback.
…sport - Move MCP endpoints to listen cleanly on /mcp - Fallback to unauthenticated HTTP sidecar when running without identity - Add get_login_instructions tool and help_user_login prompt for graceful failure - Remove --stdio flag and StdioTransport logic - Fix E2E test timeout race condition in policy.bats - Update all documentation and test cases to use the new /mcp endpoint
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request simplifies the MCP endpoint URL from /mcp/events to /mcp across the codebase, introduces an unauthenticated sidecar server mode when starting a node without an identity, adds cross-mesh discovery tools with pagination support, and refactors the Biscuit authentication middleware to prevent cache bypass issues. The review feedback highlights a potential panic in the pagination logic if a negative cursor is provided, a security vulnerability due to missing HTTP timeouts on the new unauthenticated sidecar server, and a loose assertion in the new E2E mesh test script.
| server := &http.Server{ | ||
| Handler: mux, | ||
| } |
There was a problem hiding this comment.
The newly introduced startUnauthSidecarServer initializes an http.Server without setting any timeouts (ReadTimeout, WriteTimeout, IdleTimeout). This leaves the server vulnerable to Slowloris attacks and resource exhaustion. It is highly recommended to configure reasonable timeouts.
server := &http.Server{
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request simplifies the MCP endpoint path from /mcp/events to /mcp across all configuration files, documentation, and tests. It also introduces support for starting an unauthenticated sidecar server when a node is run without an identity, enabling enrollment over MCP. Additionally, new cross-mesh discovery tools and prompts have been added (find_remote_resources, read_remote_resource, find_remote_prompts, and get_remote_prompt) along with pagination support. Finally, a security bug in the authorization cache has been resolved by including the protocol and target in the cache key. Feedback on these changes highlights potential nil pointer dereferences when parsing remote resources and prompts, as well as an anti-pattern of using defer inside a loop in handleReadRemoteResource.
| listRes, err := session.ListResources(ctx, &mcp.ListResourcesParams{}) | ||
| if err == nil && listRes != nil { | ||
| for _, r := range listRes.Resources { | ||
| // Namespace the URI scheme or name if we want, but resources are URIs. | ||
| // We'll prefix the Name to indicate origin. | ||
| r.Name = svc.Name + "." + r.Name | ||
| allResources = append(allResources, r) | ||
| } | ||
| } |
There was a problem hiding this comment.
In fetchRemoteResourceCatalogue, there is no check to ensure that the resource pointer r is not nil. If a remote peer returns a list containing null elements, this will cause a nil pointer dereference panic, crashing the node. We should add a defensive nil check.
listRes, err := session.ListResources(ctx, &mcp.ListResourcesParams{})
if err == nil && listRes != nil {
for _, r := range listRes.Resources {
if r == nil {
continue
}
// Namespace the URI scheme or name if we want, but resources are URIs.
// We'll prefix the Name to indicate origin.
r.Name = svc.Name + "." + r.Name
allResources = append(allResources, r)
}
}| listRes, err := session.ListPrompts(ctx, &mcp.ListPromptsParams{}) | ||
| if err == nil && listRes != nil { | ||
| for _, p := range listRes.Prompts { | ||
| p.Name = svc.Name + "." + p.Name | ||
| allPrompts = append(allPrompts, p) | ||
| } | ||
| } |
There was a problem hiding this comment.
In fetchRemotePromptCatalogue, there is no check to ensure that the prompt pointer p is not nil. If a remote peer returns a list containing null elements, this will cause a nil pointer dereference panic, crashing the node. We should add a defensive nil check.
listRes, err := session.ListPrompts(ctx, &mcp.ListPromptsParams{})
if err == nil && listRes != nil {
for _, p := range listRes.Prompts {
if p == nil {
continue
}
p.Name = svc.Name + "." + p.Name
allPrompts = append(allPrompts, p)
}
}| if err == nil && res != nil && len(res.Contents) > 0 { | ||
| defer cleanup() | ||
|
|
||
| // Marshal the contents | ||
| data, _ := json.Marshal(res.Contents) | ||
| return &mcp.CallToolResult{ | ||
| Content: []mcp.Content{ | ||
| &mcp.TextContent{Text: string(data)}, | ||
| }, | ||
| }, nil, nil | ||
| } | ||
| cleanup() |
There was a problem hiding this comment.
In handleReadRemoteResource, defer cleanup() is used inside a for loop. In Go, defer statements are evaluated when the surrounding function returns, not when the loop block ends. Although the function returns immediately in this branch, using defer inside a loop is a known anti-pattern and can lead to resource leaks if the code is refactored in the future to not return immediately. It is cleaner and safer to call cleanup() directly.
| if err == nil && res != nil && len(res.Contents) > 0 { | |
| defer cleanup() | |
| // Marshal the contents | |
| data, _ := json.Marshal(res.Contents) | |
| return &mcp.CallToolResult{ | |
| Content: []mcp.Content{ | |
| &mcp.TextContent{Text: string(data)}, | |
| }, | |
| }, nil, nil | |
| } | |
| cleanup() | |
| if err == nil && res != nil && len(res.Contents) > 0 { | |
| cleanup() | |
| // Marshal the contents | |
| data, _ := json.Marshal(res.Contents) | |
| return &mcp.CallToolResult{ | |
| Content: []mcp.Content{ | |
| &mcp.TextContent{Text: string(data)}, | |
| }, | |
| }, nil, nil | |
| } | |
| cleanup() |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request simplifies the MCP endpoint path from /mcp/events to /mcp across the codebase, documentation, and tests. It also introduces an unauthenticated sidecar server that starts when a node runs without an identity, providing enrollment instructions over MCP, and removes the fallback re-enrollment logic from the authorization middleware. Feedback on these changes highlights two issues: first, a bug in the newly added E2E test script (test-mesh-e2e.sh) where the jq filter incorrectly expects an items key from the find_remote_tools output; second, a logic issue in main.go where the fallback for displayHub will not trigger because hubAddr is never empty due to its default value.
| peer="$(printf '%s' "$tools" \ | ||
| | jq -r '.items[]? | select(.tool_name=="calculator.add") | .peer_id' 2>/dev/null \ | ||
| | head -n1 || true)" |
There was a problem hiding this comment.
The output of find_remote_tools is a JSON array of tool objects, not an object containing an items key. Using .items[]? in the jq filter will result in an empty string, causing the E2E test to fail to discover the tool and eventually timeout. It should be updated to use .[]? instead.
| peer="$(printf '%s' "$tools" \ | |
| | jq -r '.items[]? | select(.tool_name=="calculator.add") | .peer_id' 2>/dev/null \ | |
| | head -n1 || true)" | |
| peer="$(printf '%s' "$tools" \\ | |
| | jq -r '.[]? | select(.tool_name=="calculator.add") | .peer_id' 2>/dev/null \\ | |
| | head -n1 || true)" |
| displayHub := hubAddr | ||
| if displayHub == "" { | ||
| if h, err := store.LoadHubURL(); err == nil && h != "" { | ||
| displayHub = h | ||
| } else { | ||
| displayHub = "https://bananas.sam-mesh.dev" | ||
| } | ||
| } |
There was a problem hiding this comment.
The hubAddr variable is registered with a default value of DefaultHubURL ("http://localhost:8080"), meaning displayHub will never be empty under normal execution. As a result, the fallback block to load the stored hub URL or default to the public bananas mesh will never be executed. Consider checking if displayHub is equal to DefaultHubURL or empty.
| displayHub := hubAddr | |
| if displayHub == "" { | |
| if h, err := store.LoadHubURL(); err == nil && h != "" { | |
| displayHub = h | |
| } else { | |
| displayHub = "https://bananas.sam-mesh.dev" | |
| } | |
| } | |
| displayHub := hubAddr | |
| if displayHub == DefaultHubURL || displayHub == "" { | |
| if h, err := store.LoadHubURL(); err == nil && h != "" { | |
| displayHub = h | |
| } else { | |
| displayHub = "https://bananas.sam-mesh.dev" | |
| } | |
| } |
No description provided.